#include "Tracer.h"
#include "BaseType.h"
#include "File.h"
#include "Utils.h"
#include "BmpImage.h"

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#include <iostream>

#ifndef PAGE_MASK
#define PAGE_MASK 0x00000fff
#endif

#define RGB565(r,g,b)    ((((uint16)(r&0x001F))<<11)|(((uint16)(g&0x003F))<<5)|((uint16)(b&0x001F)))
#define RGB565_R(rgb)    (((rgb)&0xF800)>>8)   
#define RGB565_G(rgb)    (((rgb)&0x07E0)>>3)
#define RGB565_B(rgb)    (((rgb)&0x001F)<<3)

class FbScreen{
public:
    FbScreen(){};
    ~FbScreen(){};
    bool open(std::string fbName)
    {
        m_dev = ::open(fbName.c_str(), O_RDWR); 
        if(m_dev <= 0)
        {
            TRACE_ERR_CLASS("Error opening %s error:%s\n",fbName.c_str(),ERROR_STRING);
            return false;
        }
        if (-1==ioctl(m_dev,FBIOGET_VSCREENINFO,&(m_varInfo)))
        {
            TRACE_ERR_CLASS("ioctl FBIOGET_VSCREENINFO\n");
            return false;
        }
        if (-1 == ioctl(m_dev,FBIOGET_FSCREENINFO,&(m_fixInfo)))
        {
            TRACE_ERR_CLASS("ioctl FBIOGET_FSCREENINFO\n");
        }
        return true;
    }
    bool close()
    {
        ::close(m_dev);
        m_dev=-1;
        return true;
    }
    bool init()
    {
        /* 映射物理内存到虚拟内存 */
        m_memOffset = (unsigned long)(m_fixInfo.smem_start)&(~PAGE_MASK);
        m_memSize = m_varInfo.xres * m_varInfo.yres * m_varInfo.bits_per_pixel/8;
        m_mem = (unsigned long int)mmap(NULL, m_memOffset+m_memSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_dev, 0);
        if (-1L == (long)m_mem)
        {
            TRACE_ERR_CLASS("mmap error! mem:%d offset:%d\n", m_mem, m_memOffset);
            return false;
        }
        return true;
    }
    void showScreenInfo()
    {
        if(m_dev<=0)
        {
            TRACE_ERR_CLASS("fb device not open!!!\n");
            return;
        }
        TRACE_REL_CLASS("bit per pixel:%d\n"
                        "resolution: xres(%d),yres(%d)\n"
                        "virtual resolution: xres(%d),yres(%d)\n"
                        "offset: xoffset(%d),yoffset(%d)\n"
                        "size(mm): width(%d),height(%d)\n"
                        "user space address: 0x%08x, len(%d)\n",
                        m_varInfo.bits_per_pixel,
                        m_varInfo.xres,m_varInfo.yres,
                        m_varInfo.xres_virtual,m_varInfo.yres_virtual,
                        m_varInfo.xoffset,m_varInfo.yoffset,
                        m_varInfo.width,m_varInfo.height,
                        m_mem,m_fixInfo.smem_len);
    }
    void setScreenColor(char r, char g, char b)
    {
        char *startAddr = (char*)(m_mem+m_memOffset);
        int pixBytes = m_varInfo.bits_per_pixel/8;
        for (uint32 y=0; y<m_varInfo.yres; y++)
        for (uint32 x=0; x<m_varInfo.xres; x++) 
        {
            uint16 pixVal=RGB565(r,g,b);
            memcpy((void *)(startAddr+(x*y+x)*pixBytes), &pixVal, sizeof(pixVal));
            if (x==0 && y==0) 
            {
                /* 大端字节序下 Blue:001F Red:F800 Green:07E0*/
                /* 小端字节序下 Blue:1F00 Red:00F8 Green:E007*/
                TRACE_DBG_CLASS("Pixel Color on Full Screen:0x%02x 0x%02x\n",((char*)&pixVal)[0],((char*)&pixVal)[1]);
            }
         }
    }
    void dumpScreen(std::string fileName)
    {
        BmpImage bmpImage;
        char *startAddr = (char*)(m_mem+m_memOffset);
        int rawFormat;
        int pixBytes = m_varInfo.bits_per_pixel/8;
        switch (pixBytes) {
        case 2:
            rawFormat=IMAGE_RAW_FORMAT_RGB565;
            break;
        case 3:
            rawFormat=IMAGE_RAW_FORMAT_RGB888;
            break;
        case 4:
            break;
        default:
            return;
        }
        if (!bmpImage.loadFromRawData(m_varInfo.xres, m_varInfo.yres, rawFormat, startAddr, m_memSize))
        {
            TRACE_ERR_CLASS("load fb raw data error.\n");
            return;
        }
        bmpImage.saveAsFile(fileName);
    }
private:
    int m_dev;
    unsigned long m_memOffset;
    unsigned long m_mem;
    unsigned long m_memSize;
    struct fb_fix_screeninfo m_fixInfo;
    struct fb_var_screeninfo m_varInfo;
};

//#define FB_DEV      "/dev/fb0"
#define FB_DEV      "/dev/graphics/fb0"

void TestFBScreen()
{
    std::string inputStr;
    TRACE_YELLOW("input fb dev name:\n");
    std::cin >> inputStr;
    std::cout << inputStr<<std::endl;

    FbScreen fbScreen;
    if(!fbScreen.open(inputStr))
    {
        TRACE_ERR("FBScreen open(%s) error!\n",inputStr.c_str());
        return;
    }
    fbScreen.showScreenInfo();
    if(!fbScreen.init())
    {
        TRACE_ERR("FBScreen init(%s) error!\n",inputStr.c_str());
        return;
    }
    
	while(1)
	{
		TRACE_YELLOW("q ---> quit.\n");
		TRACE_YELLOW("1 ---> getScreenInfo.\n");
		TRACE_YELLOW("2 ---> set black screen.\n");
		TRACE_YELLOW("3 ---> set white screen.\n");
		TRACE_YELLOW("4 ---> set red screen.\n");
        TRACE_YELLOW("5 ---> set green screen.\n");
        TRACE_YELLOW("6 ---> set blue screen.\n");
        TRACE_YELLOW("7 ---> dump screen.\n");
		std::cin >> inputStr;
        std::cout << inputStr<<std::endl;
		if("q"==inputStr) 
		{
			break;
		}
		else if("1"==inputStr)
        {
            fbScreen.showScreenInfo();
        }
        else if("2"==inputStr)
        {
            fbScreen.setScreenColor(0,0,0); 
        }
        else if("3"==inputStr)
        {
            fbScreen.setScreenColor(255,255,255);
        }
        else if("4"==inputStr)
        {
            fbScreen.setScreenColor(255,0,0); 
        }
        else if("5"==inputStr)
        {
            fbScreen.setScreenColor(0,255,0);
        }
        else if("6"==inputStr)
        {
            fbScreen.setScreenColor(0,0,255);
        }
        else if("7"==inputStr)
        {
            TRACE_YELLOW("input bmp file name:\n");
            std::cin >> inputStr;
            std::cout << inputStr<<std::endl;
            fbScreen.dumpScreen(inputStr.c_str()); 
        }
        inputStr.clear();
    }
    fbScreen.close();
}
